﻿using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using O1.Kernel.Storage.Transactions;
using FluentAssertions;

namespace Tests.O1.Kernel.Storage.Transactions
{
    [TestClass]
    public class MultiVersionArrayTests
    {
        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void MultiVersionArrayReadsNullByDefault()
        {
            var subject = new MultiVersionArray<object>(100);
            subject.Read(1, 0).Should().BeNull();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void MultiVersionArrayLegnthIsAsInitialized()
        {
            var subject = new MultiVersionArray<object>(100);
            subject.Length.Should().Be(100);
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void MultiVersionArrayLengthIsAsExpanded()
        {
            var subject = new MultiVersionArray<object>(100);
            subject.ExpandToIndex(4095);
            subject.Length.Should().Be(4096);
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void MultiVersionArrayLengthIsAsCurrentIfExpandedWithinBounds()
        {
            var subject = new MultiVersionArray<object>(100);
            subject.ExpandToIndex(50);
            subject.Length.Should().Be(100);
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void MultiVersionArrayLengthIsAsExpandedWhenExpandingBeyondCapacity()
        {
            var subject = new MultiVersionArray<object>(100);
            var newSize = subject.Capacity + 100;
            subject.ExpandToIndex(newSize);
            subject.Length.Should().Be(newSize + 1);
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void MultiVersionArrayWritesCommitsAndReadsVisibleValue()
        {
            var subject = new MultiVersionArray<object>(100);
            var data1 = new object();
            subject.Write(0, data1, 2, 1);
            subject.Commit(0, 2);

            var data2 = subject.Read(0, 3);
            data2.Should().NotBeNull();
            data2.Should().BeSameAs(data1);
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void MultiVersionArrayHidesVisibilityForLaterPendingTransactions()
        {
            var subject = new MultiVersionArray<object>(100);
            var data1 = new object();
            subject.Write(0, data1, 2, 1);
            subject.Commit(0, 2);

            var data3 = new object();
            subject.Write(0, data3, 4, 2);

            var data2 = subject.Read(0, 2);
            data2.Should().NotBeNull();
            data2.Should().BeSameAs(data1);
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void MultiVersionArrayHidesVisibilityForLaterCommittedTransactions()
        {
            var subject = new MultiVersionArray<object>(100);
            var data1 = new object();
            subject.Write(0, data1, 1, 0);
            subject.Commit(0, 1);

            var data3 = new object();
            subject.Write(0, data3, 4, 1);
            subject.Commit(0, 4);

            var data2 = subject.Read(0, 2);
            data2.Should().NotBeNull();
            data2.Should().BeSameAs(data1);
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void MultiVersionArrayCollectOnEmptySlotDoesNotThrow()
        {
            var subject = new MultiVersionArray<object>(100);
            Action test = () => subject.Collect(50, 1);
            test.ShouldNotThrow();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void MultiVersionArrayCommitOnEmptySlotDoesNotThrow()
        {
            var subject = new MultiVersionArray<object>(100);
            Action test = () => subject.Commit(50, 1);
            test.ShouldNotThrow();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void MultiVersionArrayDeleteBeyondCapacityReturnsInfinity()
        {
            var subject = new MultiVersionArray<object>(100);
            subject.Delete(subject.Capacity, 1, 0).Should().Be(MultiVersion.Infinity);
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void MultiVersionArrayCollectOnEmptySlotIsTrue()
        {
            var subject = new MultiVersionArray<object>(100);
            subject.Collect(90, 0).Should().BeTrue();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void MultiVersionArrayTryReadVersionReturnsFalseIfIndexIsGreaterThanLength()
        {
            var subject = new MultiVersionArray<object>(100);
            MultiVersion<object> value;
            subject.TryReadVersion(105, 1, out value).Should().BeFalse();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void MultiVersionArrayTryReadVersionReturnsFalseIfIndexValueIsUnassigned()
        {
            var subject = new MultiVersionArray<object>(100);
            MultiVersion<object> value;
            subject.TryReadVersion(15, 1, out value).Should().BeFalse();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void MultiVersionArrayTryReadVersionReturnsFalseIfIndexIsEqualToLength()
        {
            var subject = new MultiVersionArray<object>(100);
            MultiVersion<object> value;
            subject.TryReadVersion(100, 1, out value).Should().BeFalse();
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void MultiVersionArrayWritePastLengthTriggersLengthExpansion()
        {
            var subject = new MultiVersionArray<object>(100);
            subject.Write(200, new object(), 1, 0);
            subject.Length.Should().Be(201);
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void MultiVersionArrayCollectsCommittedVersionUpToWatermark()
        {
            var subject = new MultiVersionArray<object>(100);
            var data1 = new object();
            subject.Write(0, data1, 1, 0);
            subject.Commit(0, 1);

            var data3 = new object();
            subject.Write(0, data3, 4, 1);
            subject.Commit(0, 4);

            subject.Collect(0, 4).Should().BeTrue();

            // NOTE: Setting the watermark to 4 implicitly means all earlier 
            // transactions have committed or rolled back. This is an artificial setup, for testing:
            var data2 = subject.Read(0, 2);
            data2.Should().BeNull();

            var data4 = subject.Read(0, 5);
            data4.Should().NotBeNull();
            data4.Should().BeSameAs(data3);
        }

        [TestCategory("Tx")]
        [TestMethod, TestCategory("Unit Test")]
        public void MultiVersionArrayCollectsCommittedVersionWithDeletedValueRemoved()
        {
            var subject = new MultiVersionArray<object>(100);
            var data1 = new object();
            subject.Write(0, data1, 1, 0);
            subject.Commit(0, 1);

            var data3 = new object();
            subject.Delete(0, 4, 1);
            subject.Commit(0, 4);

            subject.Collect(0, 4).Should().BeTrue();

            var data2 = subject.Read(0, 5);
            data2.Should().BeNull();
        }
    }
}
